﻿//===============================================================================
// Jammer.NET Development Library
// Jmr.Serialization
//===============================================================================
// Copyright © Junaid Fakhruddin, Matthew Leedham and Randolph Cabral.  
// All rights reserved.
//===============================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

namespace Jmr.Serialization
{
	/// <summary>
	/// EntityFormatter class. Provides custom formatting of Objects to String (Alternative to JSON).
	/// </summary>
	public class EntityFormatter
	{
		/// <summary>
		/// Serializes an object to a string.
		/// </summary>
		/// <typeparam name="TEntity">Type of the entity to serialize.</typeparam>
		/// <param name="entityref">Entity instance to serialize.</param>
		/// <returns>Returns a string.</returns>
		public string Serialize<TEntity>(TEntity entityref)
		{
			string retval = string.Empty;
			if (entityref is IEnumerable)
			{
				foreach (object obj in (IEnumerable)entityref)
				{
					retval += string.Format("{0}|", SerializeSingle(obj));
				}
			}
			else
			{
				retval = SerializeSingle(entityref);
			}
			retval = retval.TrimEnd(new char[] { '|' });
			return retval;
		}

		private string SerializeSingle(object entityref)
		{
			string retval = string.Empty;
			var objtype = entityref.GetType();
			var properties = objtype.GetProperties();
			retval = string.Format("class={0}*{1}:", Encode(objtype.Assembly.FullName), Encode(objtype.FullName));
			foreach (var property in properties)
			{
				Type propType = null;
				if (property.PropertyType.Name.StartsWith("Nullable"))
					propType = Nullable.GetUnderlyingType(property.PropertyType);
				else
					propType = property.PropertyType;

				if (!propType.IsPrimitive
					&& propType.FullName != "System.String"
					&& propType.FullName != "System.DateTime") { continue; }

				string name = property.Name;
				object rawvalue = property.GetValue(entityref, null);
				string value = (rawvalue == null) ? "NULL" : Encode(rawvalue.ToString());
				retval += string.Format("{0}={1}:", name, value);
			}
			retval = retval.TrimEnd(new char[] { ':' });
			return retval;
		}

		/// <summary>
		/// Deserializes a string to an object.
		/// </summary>
		/// <typeparam name="TEntity">Type of the entity to deserialize.</typeparam>
		/// <param name="formattedString">String to deserialize.</param>
		/// <returns>Returns an object.</returns>
		public TEntity Deserialize<TEntity>(string formattedString)
		{
			TEntity retval = Activator.CreateInstance<TEntity>();
			if (retval is IList)
			{
				Type generictype = retval.GetType().GetGenericArguments()[0];
				string[] objstrings = formattedString.Split(new char[] { '|' });
				foreach (string objstring in objstrings)
				{
					((IList)retval).Add(DeserializeSingle(generictype, objstring));
				}
			}
			else
			{
				if (formattedString.Contains("|"))
					throw new TypeLoadException(string.Format("The serialized string represents more than one entity.  Try passing List<{0}> as the generic argument type.", typeof(TEntity).Name));

				retval = (TEntity)DeserializeSingle(typeof(TEntity), formattedString);
			}
			return retval;
		}

		/// <summary>
		/// Deserializes a string to an object.
		/// </summary>
		/// <param name="formattedString">String to deserialize.</param>
		/// <returns>Returns an object.</returns>
		public object Deserialize(string formattedString)
		{
			var retval = default(object);

			if (formattedString != "NULL" && !string.IsNullOrEmpty(formattedString))
				if (formattedString.Contains("|"))
					retval = DeserializeEnumerable(formattedString);
				else
					retval = DeserializeSingle(GetClassType(formattedString), formattedString);

			return retval;
		}

		private IEnumerable DeserializeEnumerable(string formattedString)
		{
			var strarr = formattedString.Split(new char[] { '|' });
			var entityType = GetClassType(strarr[0]);
			var retval = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(entityType));

			for (int i = 0; i < strarr.Length; i++)
			{
				retval.Add(DeserializeSingle(entityType,strarr[i]));
			}
			
			return (IEnumerable)retval;
		}

		/// <summary>
		/// Gets the class type of a single serialized entity.
		/// </summary>
		/// <param name="formattedSingle">A serialized string of a single entity.</param>
		/// <returns>Returns a type.</returns>
		public Type GetClassType(string formattedSingle)
		{
			Type retVal = default(Type);
			string[] propertyvals = formattedSingle.Split(new char[] { ':' });
			foreach (var propval in propertyvals)
			{
				string[] nameval = propval.Split(new char[] { '=' });
				string name = nameval[0];
				string value = Decode(nameval[1]);

				if (name == "class")
				{
					retVal = ParseType(value);
					break;
				}
			}

			return retVal;
		}

		private object DeserializeSingle(Type entitytype, string formattedString)
		{
			object retval = Activator.CreateInstance(entitytype);
			string[] propertyvals = formattedString.Split(new char[] { ':' });
			foreach (var propval in propertyvals)
			{
				string[] nameval = propval.Split(new char[] { '=' });
				string name = nameval[0];
				string value = Decode(nameval[1]);

				if (name == "class")
					continue;

				value = (value == "NULL") ? null : value;
				PropertyInfo pi = retval.GetType().GetProperty(name);

				Type propType = null;
				if (pi.PropertyType.Name.StartsWith("Nullable"))
					propType = Nullable.GetUnderlyingType(pi.PropertyType);
				else
					propType = pi.PropertyType;

				if (value != null)
					pi.SetValue(retval, Convert.ChangeType(value, propType, CultureInfo.CurrentUICulture), null);
			}
			return retval;
		}

		/// <summary>
		/// Encodes special characters of a string to be serialized.
		/// </summary>
		/// <param name="value">String to encode.</param>
		/// <returns>Returns an encoded string.</returns>
		public string Encode(string value)
		{
			string retval = string.Empty;
			retval = value.Replace("=", "[jmr.e]").Replace("|", "[jmr.p]").Replace(":", "[jmr.c]");
			return retval;
		}

		/// <summary>
		/// Decodes an encoded string.
		/// </summary>
		/// <param name="value">String to decode.</param>
		/// <returns>Returns an decoded string.</returns>
		public string Decode(string value)
		{
			string retval = string.Empty;
			retval = value.Replace("[jmr.e]", "=").Replace("[jmr.p]", "|").Replace("[jmr.c]", ":");
			return retval;
		}

		private string ParseAssemblyName(string fullname)
		{
			string retval = default(string);
			retval = fullname.Split(new char[] { '*' })[0];
			//retval = fullname.Substring(0, fullname.Length - (fullname.Length - fullname.LastIndexOf(".")));
			return retval;
		}

		private string ParseTypeName(string fullname)
		{
			string retval = default(string);
			retval = fullname.Split(new char[] { '*' })[1];
			//retval = fullname.Substring(fullname.LastIndexOf(".") + 1, fullname.Length - fullname.LastIndexOf(".") - 1);
			return retval;
		}

		private object GetInstance(string fullname)
		{
			object retval = default(object);
			retval = Activator.CreateInstance(ParseType(fullname));
			return retval;
		}

		private Type ParseType(string fullname)
		{
			Type retval = default(Type);
			string assemblyName = ParseAssemblyName(fullname);
			string typeName = ParseTypeName(fullname);
			retval = System.Reflection.Assembly.Load(assemblyName).GetType(typeName);
			return retval;
		}
	}
}
